package com.carleasoft.mps.security.permission.data;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.handlers.AbstractSqlParserHandler;
import com.carleasoft.mps.core.perm.ThreadPermDataContext;
import com.carleasoft.mps.core.user.Constants;
import com.carleasoft.mps.core.user.ThreadUserContext;
import com.google.common.base.Joiner;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Properties;

@Intercepts({
        @Signature(type = Executor.class,
                method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
@Slf4j
@Component
public class DataPermissionInterceptor extends AbstractSqlParserHandler implements Interceptor {

    public Object intercept(Invocation invocation) throws Throwable {

        if(ThreadSessionContext.getCurrentMethods()==null||ThreadSessionContext.getCurrentMethods().size()==0){
            return invocation.proceed();
        }
        List<RequiresDataPermission.Scope> scopes= ThreadSessionContext.getCurrentMethods();
        //获取执行参数
        Object[] objects = invocation.getArgs();
        MappedStatement ms = (MappedStatement) objects[0];
        //解析执行sql的map方法,开始自定义规则匹配逻辑
        String mapperMethodAllName = ms.getId();
        int lastIndex = mapperMethodAllName.lastIndexOf(".");
        String mapperClassStr = mapperMethodAllName.substring(0, lastIndex);
        String mapperClassMethodStr = mapperMethodAllName.substring((lastIndex + 1));
        // Class<?> mapperClass = Class.forName(mapperClassStr);
        scopes.forEach(scope -> {
            if(mapperClassMethodStr.contains(scope.method())){
                BoundSql boundSql = ms.getSqlSource().getBoundSql(objects[1]);
                // 执行的SQL语句
                String originalSql = boundSql.getSql();
                // SQL语句的参数
                Object parameterObject = boundSql.getParameterObject();
                originalSql = "select * from (" + originalSql + ") temp_data_scope";
                switch (scope.scope()){
                    case Constants.DATA_SCOPE_ORG:
                        if (CollUtil.isNotEmpty(ThreadPermDataContext.getCurrentDataPermCache().getOrganizationCodes())){
                            originalSql=originalSql+"  where temp_data_scope.ORGANIZATION_CODE in ("+ Joiner.on(",").join(ThreadPermDataContext.getCurrentDataPermCache().getOrganizationCodes()) +")";
                            break;
                        }
                        else {
                            originalSql=originalSql+"  where temp_data_scope.ORGANIZATION_CODE  in  ('null')";
                            break;
                        }
                    case Constants.DATA_SCOPE_DEPT:
                        if (CollUtil.isNotEmpty(ThreadPermDataContext.getCurrentDataPermCache().getDeptCodes())) {
                            originalSql = originalSql + "  where temp_data_scope.DEPT_CODE in (" + Joiner.on(",").join(ThreadPermDataContext.getCurrentDataPermCache().getDeptCodes()) + ")";
                            break;
                        }else {
                            originalSql = originalSql + "  where temp_data_scope.DEPT_CODE in ('null')";
                            break;
                        }
                    case Constants.DATA_SCOPE_OWNER:
                        if (StrUtil.isNotBlank(ThreadUserContext.getCurrentCacheUser().getUserCode())) {
                            originalSql = originalSql + "  where temp_data_scope.CREATE_PERSON_CODE ='" + ThreadUserContext.getCurrentCacheUser().getUserCode() + "'";
                            break;
                        }
                        break;
                    case Constants.DATA_SCOPE_DEPT_BY_CREATE_PERSON_CODE:
                        if (CollUtil.isNotEmpty(ThreadPermDataContext.getCurrentDataPermCache().getDeptCodes())) {
                            originalSql = originalSql + "  temp_data_scope INNER JOIN  m_authority_user_dept maud \n" +
                                    "on temp_data_scope.CREATE_PERSON_CODE = maud.USER_CODE    AND   maud.DEPT_CODE  in in (" + Joiner.on(",").join(ThreadPermDataContext.getCurrentDataPermCache().getDeptCodes()) + ")";
                            break;
                        }else {
                            originalSql = originalSql + "  temp_data_scope INNER JOIN  m_authority_user_dept maud \n" +
                                    "on temp_data_scope.CREATE_PERSON_CODE = maud.USER_CODE    AND   maud.DEPT_CODE  in ('null')";
                            break;
                        }
                }
                BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), originalSql,
                        boundSql.getParameterMappings(), boundSql.getParameterObject());
                MappedStatement newMs = newMappedStatement(ms, new MyBoundSqlSqlSource(newBoundSql));
                for (ParameterMapping mapping : boundSql.getParameterMappings()) {
                    String prop = mapping.getProperty();
                    if (boundSql.hasAdditionalParameter(prop)) {
                        newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
                    }
                }
                Object[] queryArgs = invocation.getArgs();
                queryArgs[0] = newMs;
            }
        });
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Interceptor.super.plugin(target);
    }

    @Override
    public void setProperties(Properties properties) {
        Interceptor.super.setProperties(properties);
    }

    /**
     * 定义一个内部辅助类，作用是包装 SQL
     */
    class MyBoundSqlSqlSource implements SqlSource {
        private BoundSql boundSql;

        public MyBoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }

    }

    private MappedStatement newMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
        MappedStatement.Builder builder = new
                MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length > 0) {
            builder.keyProperty(ms.getKeyProperties()[0]);
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());
        return builder.build();
    }

}
